// This file is part of Core WF which is licensed under the MIT license.
// See LICENSE file in the project root for full license information.


#if DYNAMICUPDATE
using System.Activities.DynamicUpdate;
#endif

namespace System.Activities.Runtime;

[DataContract(Name = XD.Runtime.ActivityInstanceMap, Namespace = XD.Runtime.Namespace)]
internal class ActivityInstanceMap
{
    // map from activities to (active) associated activity instances
    private IDictionary<Activity, InstanceList> _instanceMapping;
    private InstanceList[] _rawDeserializedLists;

#if DYNAMICUPDATE
    IList<InstanceListNeedingUpdate> updateList;
#endif

    internal ActivityInstanceMap() { }

    //[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode,
    //    Justification = "Called by serialization")]
    [DataMember(EmitDefaultValue = false)]
    internal InstanceList[] SerializedInstanceLists
    {
        get
        {
            if (_instanceMapping == null || _instanceMapping.Count == 0)
            {
                return _rawDeserializedLists;
            }
            else
            {
                InstanceList[] lists = new InstanceList[_instanceMapping.Count];
                int index = 0;
                foreach (KeyValuePair<Activity, InstanceList> entry in _instanceMapping)
                {
                    entry.Value.ActivityId = entry.Key.QualifiedId.AsByteArray();
                    lists[index] = entry.Value;
                    index++;
                }

                return lists;
            }
        }
        set
        {
            Fx.Assert(value != null, "We don't serialize the default value.");

            _rawDeserializedLists = value;
        }
    }

    private IDictionary<Activity, InstanceList> InstanceMapping
    {
        get
        {
            _instanceMapping ??= new Dictionary<Activity, InstanceList>();
            return _instanceMapping;
        }
    }

#if DYNAMICUPDATE

    private static void AddBlockingActivity(ref Collection<ActivityBlockingUpdate> updateErrors, DynamicUpdateMap.UpdatedActivity updatedActivity, QualifiedId originalId, string reason, string activityInstanceId)
    {
        if (updatedActivity.NewActivity != null)
        {
            ActivityBlockingUpdate.AddBlockingActivity(ref updateErrors, updatedActivity.NewActivity, originalId.ToString(), reason, activityInstanceId);
        }
        else
        {
            string updatedId = updatedActivity.MapEntry.IsRemoval ? null : updatedActivity.NewId.ToString();
            ActivityBlockingUpdate.AddBlockingActivity(ref updateErrors, updatedId, originalId.ToString(), reason, activityInstanceId);
        }
    }

    public void GetActivitiesBlockingUpdate(DynamicUpdateMap updateMap, List<ActivityInstance> secondaryRootInstances, ref Collection<ActivityBlockingUpdate> updateErrors)
    {
        this.GetInstanceListsNeedingUpdate(updateMap, null, secondaryRootInstances, ref updateErrors);
    }

    // searching secondaryRootInstances list is necessary because instance in InstanceList doesn't have its Parent set until it's fixed up.
    // so the only way to find out if an instance in InstanceList is a secondary root is to lookup in secondaryRootInstances list.
    private static bool IsNonDefaultSecondaryRoot(ActivityInstance instance, List<ActivityInstance> secondaryRootInstances)
    {
        if (secondaryRootInstances != null && secondaryRootInstances.Contains(instance))
        {
            // Non-default secondary roots are CompensationParticipant type, and their environment will always have a non-null parent which is the environment owned by a CompensableActivity.
            // A secondary root whose environment parent is null is the default secondary root, WorkflowCompensationBehavior.
            if (instance.IsEnvironmentOwner && instance.Environment.Parent != null)
            {
                return true;
            }
        }

        return false;
    }

    private static bool CanCompensationOrConfirmationHandlerReferenceAddedSymbols(InstanceList instanceList, DynamicUpdateMap rootUpdateMap, IdSpace rootIdSpace, List<ActivityInstance> secondaryRootInstances, ref Collection<ActivityBlockingUpdate> updateErrors)
    {
        for (int j = 0; j < instanceList.Count; j++)
        {
            ActivityInstance activityInstance = instanceList[j] as ActivityInstance;
            if (activityInstance == null || !IsNonDefaultSecondaryRoot(activityInstance, secondaryRootInstances))
            {
                continue;
            }

            // here, find out if the given non-default secondary root references an environment to which a symbol is to be added via DU.
            // we start from a secondary root instead of starting from the enviroment with the already completed owner that was added symbols.
            // It is becuase for the case of adding symbols to noSymbols activities, the environment doesn't even exist from which we can start looking for referencing secondary root.

            int[] secondaryRootOriginalQID = new QualifiedId(instanceList.ActivityId).AsIDArray();

            Fx.Assert(secondaryRootOriginalQID != null && secondaryRootOriginalQID.Length > 1,
                "CompensationParticipant is always an implementation child of a CompensableActivity, therefore it's IdSpace must be at least one level deep.");

            int[] parentOfSecondaryRootOriginalQID = new int[secondaryRootOriginalQID.Length - 1];
            Array.Copy(secondaryRootOriginalQID, parentOfSecondaryRootOriginalQID, secondaryRootOriginalQID.Length - 1);

            List<int> currentQIDBuilder = new List<int>();
            for (int i = 0; i < parentOfSecondaryRootOriginalQID.Length; i++)
            {
                // 
                // for each iteration of this for-loop, 
                //  we are finding out if at every IdSpace level the map has any map entry whose activity has the CompensableActivity as an implementation decendant.
                //  The map may not exist for every IdSpace between the root and the CompensableActivity.
                //  If the matching map and the entry is found, then we find out if that matching entry's activity is a public decendant of any NoSymbols activity DU is to add variables or arguments to.
                //
                // This walk on the definition activity tree determines the hypothetical execution-time chain of instances and environments.
                // The ultimate goal is to prevent adding variables or arguments to a NoSymbols activity which has already completed,
                //  but its decendant CompensableActivity's compensation or confirmation handlers in the future may need to reference the added variables or arguments.

                currentQIDBuilder.Add(parentOfSecondaryRootOriginalQID[i]);

                DynamicUpdateMap.UpdatedActivity updatedActivity = rootUpdateMap.GetUpdatedActivity(new QualifiedId(currentQIDBuilder.ToArray()), rootIdSpace);
                if (updatedActivity.MapEntry != null)
                {
                    // the activity of this entry either has the CompensableActivity as an implementation decendant, or is the CompensableActivity itself.

                    // walk the same-IdSpace-parent chain of the entry,
                    // look for an entry whose EnvironmentUpdateMap.IsAdditionToNoSymbols is true.
                    DynamicUpdateMapEntry entry = updatedActivity.MapEntry;
                    do
                    {
                        if (!entry.IsRemoval && entry.HasEnvironmentUpdates && entry.EnvironmentUpdateMap.IsAdditionToNoSymbols)
                        {
                            int[] noSymbolAddActivityIDArray = currentQIDBuilder.ToArray();
                            noSymbolAddActivityIDArray[noSymbolAddActivityIDArray.Length - 1] = entry.OldActivityId;
                            QualifiedId noSymbolAddActivityQID = new QualifiedId(noSymbolAddActivityIDArray);
                            DynamicUpdateMap.UpdatedActivity noSymbolAddUpdatedActivity = rootUpdateMap.GetUpdatedActivity(noSymbolAddActivityQID, rootIdSpace);

                            AddBlockingActivity(ref updateErrors, noSymbolAddUpdatedActivity, noSymbolAddActivityQID, SR.VariableOrArgumentAdditionToReferencedEnvironmentNoDUSupported, null);
                            return true;
                        }

                        entry = entry.Parent;
                    } while (entry != null);
                }
            }
        }

        return false;
    }

    private static bool IsInvalidEnvironmentUpdate(InstanceList instanceList, DynamicUpdateMap.UpdatedActivity updatedActivity, ref Collection<ActivityBlockingUpdate> updateErrors)
    {
        if (updatedActivity.MapEntry == null || !updatedActivity.MapEntry.HasEnvironmentUpdates)
        {
            return false;
        }

        for (int j = 0; j < instanceList.Count; j++)
        {
            ActivityInstance activityInstance = instanceList[j] as ActivityInstance;
            if (activityInstance != null)
            {
                string error = null;
                if (activityInstance.SubState == ActivityInstance.Substate.ResolvingVariables)
                {
                    // if the entry has Environment update to do when the instance is in the middle of resolving variable, it is an error.
                    error = SR.CannotUpdateEnvironmentInTheMiddleOfResolvingVariables;
                }
                else if (activityInstance.SubState == ActivityInstance.Substate.ResolvingArguments)
                {
                    // if the entry has Environment update to do when the instance is in the middle of resolving arguments, it is an error.
                    error = SR.CannotUpdateEnvironmentInTheMiddleOfResolvingArguments;
                }

                if (error != null)
                {
                    AddBlockingActivity(ref updateErrors, updatedActivity, new QualifiedId(instanceList.ActivityId), error, activityInstance.Id);
                    return true;
                }
            }
            else
            {
                LocationEnvironment environment = instanceList[j] as LocationEnvironment;
                if (environment != null)
                {
                    //
                    // environment that is referenced by a secondary root
                    // Adding a variable or argument that requires expression scheduling to this instanceless environment is not allowed.
                    //
                    List<int> dummyIndexes;
                    EnvironmentUpdateMap envMap = updatedActivity.MapEntry.EnvironmentUpdateMap;

                    if ((envMap.HasVariableEntries && TryGatherSchedulableExpressions(envMap.VariableEntries, out dummyIndexes)) ||
                        (envMap.HasPrivateVariableEntries && TryGatherSchedulableExpressions(envMap.PrivateVariableEntries, out dummyIndexes)) ||
                        (envMap.HasArgumentEntries && TryGatherSchedulableExpressions(envMap.ArgumentEntries, out dummyIndexes)))
                    {
                        AddBlockingActivity(ref updateErrors, updatedActivity, new QualifiedId(instanceList.ActivityId), SR.VariableOrArgumentAdditionToReferencedEnvironmentNoDUSupported, null);
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private static bool IsRemovalOrRTUpdateBlockedOrBlockedByUser(DynamicUpdateMap.UpdatedActivity updatedActivity, QualifiedId oldQualifiedId, out string error)
    {
        error = null;
        if (updatedActivity.MapEntry.IsRemoval)
        {
            // TODO, 190894, this will prevent us from removing a completed activity whose
            // completion callback has not been called.
            error = SR.CannotRemoveExecutingActivityUpdateError(oldQualifiedId, updatedActivity.MapEntry.DisplayName);
        }
        else if (updatedActivity.MapEntry.IsRuntimeUpdateBlocked)
        {
            error = updatedActivity.MapEntry.BlockReasonMessage ?? UpdateBlockedReasonMessages.Get(updatedActivity.MapEntry.BlockReason);
        }
        else if (updatedActivity.MapEntry.IsUpdateBlockedByUpdateAuthor)
        {
            error = SR.BlockedUpdateInsideActivityUpdateByUserError;
        }

        return error != null;
    }

    // targetDefinition argument is optional.
    private IList<InstanceListNeedingUpdate> GetInstanceListsNeedingUpdate(DynamicUpdateMap updateMap, Activity targetDefinition, List<ActivityInstance> secondaryRootInstances, ref Collection<ActivityBlockingUpdate> updateErrors)
    {
        IList<InstanceListNeedingUpdate> instanceListsToUpdate = new List<InstanceListNeedingUpdate>();
        if (this.rawDeserializedLists == null)
        {
            // This instance doesn't have any active instances (it is complete).
            return instanceListsToUpdate;
        }

        IdSpace rootIdSpace = null;
        if (targetDefinition != null)
        {
            rootIdSpace = targetDefinition.MemberOf;
        }

        for (int i = 0; i < this.rawDeserializedLists.Length; i++)
        {
            InstanceList list = this.rawDeserializedLists[i];
            QualifiedId oldQualifiedId = new QualifiedId(list.ActivityId);

            if (updateMap.IsImplementationAsRoot)
            {
                int[] oldIdArray = oldQualifiedId.AsIDArray();
                if (oldIdArray.Length == 1 && oldIdArray[0] != 1)
                {
                    throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidImplementationAsWorkflowRootForRuntimeState));
                }
            }

            string error;
            InstanceListNeedingUpdate update;
            DynamicUpdateMap.UpdatedActivity updatedActivity = updateMap.GetUpdatedActivity(oldQualifiedId, rootIdSpace);

            if (CanCompensationOrConfirmationHandlerReferenceAddedSymbols(list, updateMap, rootIdSpace, secondaryRootInstances, ref updateErrors))
            {
                update = null;
            }
            else if (updatedActivity.MapEntry == null)
            {
                if (updatedActivity.IdChanged)
                {
                    // this newQualifiedId is the new id for those InstanceLists whose IDs shifted by their parents' ID change
                    update = new InstanceListNeedingUpdate
                    {
                        InstanceList = list,
                        NewId = updatedActivity.NewId
                    };
                }
                else
                {
                    // nothing changed, no map, no mapEntry
                    update = new InstanceListNeedingUpdate
                    {
                        InstanceList = list,
                        NewId = null,
                    };
                }
            }
            else if (updatedActivity.MapEntry.IsParentRemovedOrBlocked)
            {
                update = null;
            }
            else if (IsRemovalOrRTUpdateBlockedOrBlockedByUser(updatedActivity, oldQualifiedId, out error))
            {
                string instanceId = null;
                for (int j = 0; j < list.Count; j++)
                {
                    ActivityInstance activityInstance = list[j] as ActivityInstance;
                    if (activityInstance != null)
                    {
                        instanceId = activityInstance.Id;
                        break;
                    }
                }
                AddBlockingActivity(ref updateErrors, updatedActivity, oldQualifiedId, error, instanceId);

                update = null;
            }
            else if (IsInvalidEnvironmentUpdate(list, updatedActivity, ref updateErrors))
            {
                update = null;
            }
            else
            {
                // no validation error for this InstanceList
                // add it to the list of InstanceLists to be updated
                update = new InstanceListNeedingUpdate
                {
                    InstanceList = list,
                    NewId = updatedActivity.NewId,
                    UpdateMap = updatedActivity.Map,
                    MapEntry = updatedActivity.MapEntry,
                    NewActivity = updatedActivity.NewActivity
                };
            }

            if (update != null)
            {
                update.OriginalId = list.ActivityId;
                instanceListsToUpdate.Add(update);
            }
        }

        return instanceListsToUpdate;
    }

    public void UpdateRawInstance(DynamicUpdateMap updateMap, Activity targetDefinition, List<ActivityInstance> secondaryRootInstances, ref Collection<ActivityBlockingUpdate> updateErrors)
    {
        this.updateList = GetInstanceListsNeedingUpdate(updateMap, targetDefinition, secondaryRootInstances, ref updateErrors);
        if (updateErrors != null && updateErrors.Count > 0)
        {
            // error found.
            // there is no need to proceed to updating the instances
            return;
        }

        // if UpdateType is either MapEntryExists or ParentIdShiftOnly,
        // update the ActivityIDs and update Environments            
        // also, update the ImplementationVersion.
        foreach (InstanceListNeedingUpdate update in this.updateList)
        {
            Fx.Assert(update.InstanceList != null, "update.InstanceList must not be null.");

            if (update.NothingChanged)
            {
                continue;
            }

            Fx.Assert(update.NewId != null, "update.NewId must not be null.");

            InstanceList instanceList = update.InstanceList;
            instanceList.ActivityId = update.NewId.AsByteArray();

            if (update.ParentIdShiftOnly)
            {
                // this InstanceList must have been one of those whose IDs shifted by their parent's ID change,
                // but no involvement in DU.
                continue;
            }

            bool implementationVersionUpdateNeeded = false;
            if (update.MapEntry.ImplementationUpdateMap != null)
            {
                implementationVersionUpdateNeeded = true;
            }

            if (update.MapEntry.HasEnvironmentUpdates)
            {
                // update LocationEnvironemnt

                Fx.Assert(update.NewActivity != null, "TryGetUpdateMapEntryFromRootMap should have thrown if it couldn't map to an activity");
                instanceList.UpdateEnvironments(update.MapEntry.EnvironmentUpdateMap, update.NewActivity);
            }

            for (int i = 0; i < instanceList.Count; i++)
            {
                ActivityInstance activityInstance = instanceList[i] as ActivityInstance;

                if (implementationVersionUpdateNeeded)
                {
                    activityInstance.ImplementationVersion = update.NewActivity.ImplementationVersion;
                }
            }
        }
    }

    private static bool TryGatherSchedulableExpressions(IList<EnvironmentUpdateMapEntry> entries, out List<int> addedLocationReferenceIndexes)
    {
        addedLocationReferenceIndexes = null;

        for (int i = 0; i < entries.Count; i++)
        {
            EnvironmentUpdateMapEntry entry = entries[i];
            if (entry.IsAddition)
            {
                if (addedLocationReferenceIndexes == null)
                {
                    addedLocationReferenceIndexes = new List<int>();
                }
                addedLocationReferenceIndexes.Add(entry.NewOffset);
            }
        }

        return addedLocationReferenceIndexes != null;
    }

    // this is called after all instances have been loaded and fixedup
    public void UpdateInstanceByActivityParticipation(ActivityExecutor activityExecutor, DynamicUpdateMap rootMap, ref Collection<ActivityBlockingUpdate> updateErrors)
    {
        foreach (InstanceListNeedingUpdate participant in this.updateList)
        {
            if (participant.NothingChanged || participant.ParentIdShiftOnly)
            {
                Fx.Assert(participant.UpdateMap == null && participant.MapEntry == null, "UpdateMap and MapEntry must be null if we are here.");

                // create a temporary NoChanges UpdateMap as well as a temporary no change MapEntry
                // so that we can create a NativeActivityUpdateContext object in order to invoke UpdateInstance() on an activity which
                // doesn't have a corresponding map and an map entry.  
                // The scenario enabled here is scheduling a newly added reference branch to a Parallel inside an activity's implementation.                   
                participant.UpdateMap = DynamicUpdateMap.DummyMap;
                participant.MapEntry = DynamicUpdateMapEntry.DummyMapEntry;
            }

            // now let activities participate in update
            for (int i = 0; i < participant.InstanceList.Count; i++)
            {
                ActivityInstance instance = participant.InstanceList[i] as ActivityInstance;
                if (instance == null)
                {
                    continue;
                }

                IInstanceUpdatable activity = instance.Activity as IInstanceUpdatable;
                if (activity != null && instance.SubState == ActivityInstance.Substate.Executing)
                {
                    NativeActivityUpdateContext updateContext = new NativeActivityUpdateContext(this, activityExecutor, instance, participant.UpdateMap, participant.MapEntry, rootMap);
                    try
                    {
                        activity.InternalUpdateInstance(updateContext);

                        if (updateContext.IsUpdateDisallowed)
                        {
                            ActivityBlockingUpdate.AddBlockingActivity(ref updateErrors, instance.Activity, new QualifiedId(participant.OriginalId).ToString(), updateContext.DisallowedReason, instance.Id);
                            continue;
                        }
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }

                        throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.NativeActivityUpdateInstanceThrewException(e.Message), e));
                    }
                    finally
                    {
                        updateContext.Dispose();
                    }
                }
            }
        }

        // Schedule evaluation of newly added arguments and newly added variables.
        // This needs to happen after all the invokations of UpdateInstance above, so that newly
        // added arguments and newly added variables get evaluated before any newly added activities get executed.
        // We iterate the list in reverse so that parents are always scheduled after (and thus 
        // execute before) their children, which may depend on the parents.
        for (int i = this.updateList.Count - 1; i >= 0; i--)
        {
            InstanceListNeedingUpdate participant = this.updateList[i];

            if (!participant.MapEntryExists)
            {
                // if the given InstanceList has no map entry,
                // then there is no new LocationReferences to resolve. 
                continue;
            }

            Fx.Assert(participant.MapEntry != null, "MapEntry must be non-null here.");
            if (!participant.MapEntry.HasEnvironmentUpdates)
            {
                // if there is no environment updates for this MapEntry,
                // then there is no new LocationReferences to resolve.
                continue;
            }

            for (int j = 0; j < participant.InstanceList.Count; j++)
            {
                ActivityInstance instance = participant.InstanceList[j] as ActivityInstance;
                if (instance == null || instance.SubState != ActivityInstance.Substate.Executing)
                {
                    // if the given ActivityInstance is not in Substate.Executing, 
                    // then, do not try to resolve new LocationReferences
                    continue;
                }

                List<int> addedArgumentIndexes;
                List<int> addedVariableIndexes;
                List<int> addedPrivateVariableIndexes;

                EnvironmentUpdateMap envMap = participant.MapEntry.EnvironmentUpdateMap;

                if (envMap.HasVariableEntries && TryGatherSchedulableExpressions(envMap.VariableEntries, out addedVariableIndexes))
                {
                    // schedule added variable default expressions
                    instance.ResolveNewVariableDefaultsDuringDynamicUpdate(activityExecutor, addedVariableIndexes, false);
                }

                if (envMap.HasPrivateVariableEntries && TryGatherSchedulableExpressions(envMap.PrivateVariableEntries, out addedPrivateVariableIndexes))
                {
                    // schedule added private variable default expressions
                    // HasPrivateMemberChanged() check disallows addition of private variable default that offsets the private IdSpace,
                    // However, the added private variable default expression can be an imported activity, which has no affect on the private IdSpace.
                    // For such case, we want to be able to schedule the imported default expressions here.
                    instance.ResolveNewVariableDefaultsDuringDynamicUpdate(activityExecutor, addedPrivateVariableIndexes, true);
                }

                if (envMap.HasArgumentEntries && TryGatherSchedulableExpressions(envMap.ArgumentEntries, out addedArgumentIndexes))
                {
                    // schedule added arguments
                    instance.ResolveNewArgumentsDuringDynamicUpdate(activityExecutor, addedArgumentIndexes);
                }
            }
        }
    }

#endif
    public void AddEntry(IActivityReference reference, bool skipIfDuplicate)
    {
        Activity activity = reference.Activity;

        if (InstanceMapping.TryGetValue(activity, out InstanceList mappedInstances))
        {
            mappedInstances.Add(reference, skipIfDuplicate);
        }
        else
        {
            InstanceMapping.Add(activity, new InstanceList(reference));
        }
    }

    public void AddEntry(IActivityReference reference) => AddEntry(reference, false);

    public void LoadActivityTree(Activity rootActivity, ActivityInstance rootInstance, List<ActivityInstance> secondaryRootInstances, ActivityExecutor executor)
    {
        Fx.Assert(_rawDeserializedLists != null, "We should always have deserialized some lists.");

        _instanceMapping = new Dictionary<Activity, InstanceList>(_rawDeserializedLists.Length);

        for (int i = 0; i < _rawDeserializedLists.Length; i++)
        {
            InstanceList list = _rawDeserializedLists[i];
            if (!QualifiedId.TryGetElementFromRoot(rootActivity, list.ActivityId, out Activity activity))
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.ActivityInstanceFixupFailed));
            }
            _instanceMapping.Add(activity, list);
            list.Load(activity, this);
        }

        // We need to null this out once we've recreated the dictionary to avoid
        // having out of sync data
        _rawDeserializedLists = null;

        // then walk our instance list, fixup parent references, and perform basic validation
        Func<ActivityInstance, ActivityExecutor, bool> processInstanceCallback = new(OnActivityInstanceLoaded);

        rootInstance.FixupInstance(null, this, executor);
        ActivityUtilities.ProcessActivityInstanceTree(rootInstance, executor, processInstanceCallback);

        if (secondaryRootInstances != null)
        {
            foreach (ActivityInstance instance in secondaryRootInstances)
            {
                instance.FixupInstance(null, this, executor);
                ActivityUtilities.ProcessActivityInstanceTree(instance, executor, processInstanceCallback);
            }
        }
    }

    private bool OnActivityInstanceLoaded(ActivityInstance activityInstance, ActivityExecutor executor) => activityInstance.TryFixupChildren(this, executor);

    public bool RemoveEntry(IActivityReference reference)
    {
        if (_instanceMapping == null)
        {
            return false;
        }

        Activity activity = reference.Activity;

        if (!InstanceMapping.TryGetValue(activity, out InstanceList mappedInstances))
        {
            return false;
        }

        if (mappedInstances.Count == 1)
        {
            InstanceMapping.Remove(activity);
        }
        else
        {
            mappedInstances.Remove(reference);
        }

        return true;
    }

    [DataContract]
    internal class InstanceList : HybridCollection<IActivityReference>
    {
        public InstanceList(IActivityReference reference)
            : base(reference) { }

        //[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode,
        //    Justification = "Called by serialization")]
        [DataMember]
        public byte[] ActivityId { get; set; }

        [OnSerializing]
        //[SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters)]
        //[SuppressMessage(FxCop.Category.Usage, "CA2238:ImplementSerializationMethodsCorrectly",
        //    Justification = "Needs to be internal for serialization in partial trust. We have set InternalsVisibleTo(System.Runtime.Serialization) to allow this.")]
        internal void OnSerializing(StreamingContext context) => Compress();

        public void Add(IActivityReference reference, bool skipIfDuplicate)
        {
            Fx.Assert(Count >= 1, "instance list should never be empty when we call Add");

            if (skipIfDuplicate)
            {
                if (SingleItem != null)
                {
                    if (SingleItem == reference)
                    {
                        return;
                    }
                }
                else
                {
                    if (MultipleItems.Contains(reference))
                    {
                        return;
                    }
                }
            }

            Add(reference);
        }

        public void Load(Activity activity, ActivityInstanceMap instanceMap)
        {
            Fx.Assert(Count >= 1, "instance list should never be empty on load");
            if (SingleItem != null)
            {
                SingleItem.Load(activity, instanceMap);
            }
            else
            {
                for (int i = 0; i < MultipleItems.Count; i++)
                {
                    MultipleItems[i].Load(activity, instanceMap);
                }
            }
        }

#if DYNAMICUPDATE
        public void UpdateEnvironments(EnvironmentUpdateMap map, Activity activity)
        {
            if (base.SingleItem != null)
            {
                IActivityReferenceWithEnvironment reference = base.SingleItem as IActivityReferenceWithEnvironment;
                if (reference != null)
                {
                    reference.UpdateEnvironment(map, activity);
                }
            }
            else
            {
                for (int i = 0; i < base.MultipleItems.Count; i++)
                {
                    IActivityReferenceWithEnvironment reference = base.MultipleItems[i] as IActivityReferenceWithEnvironment;
                    if (reference != null)
                    {
                        reference.UpdateEnvironment(map, activity);
                    }
                }
            }
        } 
#endif

    }

    public interface IActivityReference
    {
        Activity Activity { get; }
        void Load(Activity activity, ActivityInstanceMap instanceMap);
    }

#if DYNAMICUPDATE

    public interface IActivityReferenceWithEnvironment : IActivityReference
    {
        void UpdateEnvironment(EnvironmentUpdateMap map, Activity activity);
    } 

    class InstanceListNeedingUpdate
    {
        // The list of IActivityReferences to be updated
        public InstanceList InstanceList { get; set; }

        public byte[] OriginalId { get; set; }

        // The new ActivityId for these ActivityReferences.
        public QualifiedId NewId { get; set; }

        // The Map & MapEntry for this ActivityId, if there is one.
        // Null if the activity's parent Id was updated, but not the activity itself,
        // Or null if nothing changed.
        public DynamicUpdateMap UpdateMap { get; set; }
        public DynamicUpdateMapEntry MapEntry { get; set; }

        // A pointer to this activity, in the new definition.
        // Null if we don't have the definition loaded.
        public Activity NewActivity { get; set; }

        // 
        // the following three properties are mutual exlusive,
        // meaning, one and only one of them evaluates to TRUE.
        //
        public bool NothingChanged
        {
            get
            {
                return this.MapEntry == null && this.NewId == null;
            }
        }

        public bool MapEntryExists
        {
            get
            {
                return this.MapEntry != null;
            }
        }

        public bool ParentIdShiftOnly
        {
            get
            {
                return this.MapEntry == null && this.NewId != null;
            }
        }        
    }

#endif
}
